#include "pgi.h"

#include <stdlib.h>
#include <stdarg.h>  // varargs
#include <string.h>  // strlen

#include "tnet.h"

/**
 * pgi.c
 *
 * Wrapper for writing PGI apps in C.
 *
 * NOTES
 * - single-threaded.  In C you can imagine thread-based, fork()-based, or
 *   event-based conconcurrency.
 *
 * TODO
 * - API for application errors?
 *   - maybe the app just needs to define error_data in its response type.
 * - Journal loading.  C is good for in-memory apps.
 */


/**
 * log diagnostic error text.  In PGI, it always goes to stderr.
 * Like tnet_log.
 */ 
void pgi_log(const char* fmt, ...) {
  va_list argptr;
  fputs(" pgi: ", stderr);
  va_start(argptr, fmt);
  vfprintf(stderr, fmt, argptr);
  fputs("\n", stderr);
  va_end(argptr);
}

//
// PGI
//

static void write_init_result(TnetContext* ctx, FILE* outfile) {
  Pair p;
  // Warning because string constants are const char*, but Buffer takes a char*
  // (and the char* can be freed).
  p.key = tnet_string_to_chunk((char*)"result");
  p.value = tnet_string_to_chunk((char*)"ok");
  p.next = NULL;

  // Dump the pairs to a buffer, print it, then deallocate.
  Buffer out;
  tnet_dump_dict(ctx, &p, &out);

  tnet_print_buf(out, outfile);

  tnet_free_buffer(out);
}

static void write_response(TnetContext* ctx, Buffer b, FILE* outfile) {
  Pair p;
  // TODO: choose something other than response_pb?
  p.next = NULL;
  p.key = tnet_string_to_chunk((char*)"response_pb");
  Chunk* v = &p.value;
  v->payload = b;
  v->tag = ',';  // byte string

  // Dump the pair to a buffer, print it, then deallocate.
  Buffer out;
  tnet_dump_dict(ctx, &p, &out);

  tnet_print_buf(out, outfile);

  tnet_free_buffer(out);
}


// A single thread to read from the input stream, dispatch to handlers, and
// write to the output.

int pgi_serve_files(pgi_dispatch_func_t dispatch_func, void* app_ctx,
                    FILE* infile, FILE *outfile) {
  TnetContext ctx;
  ctx.malloc = malloc;
  ctx.logfd = stderr;

  while (1) {
    Chunk c;
    if (!tnet_read(&ctx, infile, &c)) {
      pgi_log("Error reading request from stdin");
      return 0;
    }

    pgi_log("buf %s", c.payload.data);
    pgi_log("len %d", c.payload.len);
    pgi_log("tag %c", c.tag);

    // Now parse it into a dict.  Pair instances are allocated.  But Buffer
    // shares the underlying storage.
    Pair* pairs;
    if (!tnet_parse_dict_chunks(&ctx, c.payload, &pairs)) {
      pgi_log("Error parsing dict.");
      return 0;
    }

    //pgi_log("--- Got request");
    //tnet_print_pairs(pairs, stderr);
    //pgi_log("---");

    // Memory from the buffer in 'c' is reused here.

    // If we got { command: init }, return { result: ok }.
    Chunk command_str;
    if (tnet_pairs_getchunk(pairs, "command", &command_str)) {
      pgi_log("Got command");

      if (tnet_chunk_equals_string(command_str, "init")) {
        write_init_result(&ctx, outfile);
        continue;
      } else if (tnet_chunk_equals_string(command_str, "__debug_quit__")) {
        // Apps are normally killed with signals
        pgi_log("Got __debug_quit__");
        return 1;  // EARLY RETURN
      } else {
        pgi_log("Invalid command");
        return 0;  // error
      }
    }

    // Memory from the buffer in 'c' is reused here.
    Chunk request_chunk;
    if (tnet_pairs_getchunk(pairs, "request", &request_chunk)) {
      pgi_log("Got app request");
      //tnet_print_buf(request_chunk.payload, stderr);
    } else {
      pgi_log("Request Not found");
      return 1;  // not a hard error
    }

    Chunk route;
    if (tnet_pairs_getchunk(pairs, "route", &route)) {
      pgi_log("Got route");
      //tnet_print_buf(route.payload, stderr);
    } else {
      pgi_log("route Not found");
      return 0;
    }

    pgi_log("Invoking dispatch_func");
    Buffer response_buf;

    int len = route.payload.len;
    char* route_str = (char*)malloc(len+1);  // FIX: leak
    memcpy(route_str, route.payload.data, len);
    route_str[len] = '\0';

    dispatch_func(app_ctx, route_str,
                  request_chunk.payload.data, request_chunk.payload.len,
                  &response_buf.data, &response_buf.len);

    // Write response to stdout
    write_response(&ctx, response_buf, outfile);

    // Free response buf allocated by dispatch_func.
    tnet_free_buffer(response_buf);

    // tnet_read allocates memory that we have to free.
    tnet_free_chunk(c);

    // tnet_parse_dict allocates memory we have to free.
    tnet_free_pairs(pairs);
  }

  return 1;  // will never get here -- app will be killed
}

int pgi_serve(pgi_dispatch_func_t dispatch_func, void* app_ctx) {
    pgi_serve_files(dispatch_func, app_ctx, stdin, stdout);
}
